#define STRICT
#define ORBITER_MODULE
#define OPTS 7
#define TABLENGTH 4
#define DEBUG
//#define SPAMMYDEBUG
#include "orbitersdk.h"
#include "NotesMFD.h"

using namespace std;

flist filesList = { 0, 0, NULL };
bool listLoaded = false;
vector<string> lines;
vector<int> lineBreaks;
vector<int> preidxes;

int mode;
bool opened;
int startFileIndex;
int startLineIndex;
int fileIndex;
string fileName;
char lastshipname[256];

double btPressedTime = 0;
bool btPressedFirstStep = true;

DLLCLBK void InitModule(HINSTANCE hModule) {
	static char *name = "Notes";
	MFDMODESPEC spec;
	spec.name    = name;
	spec.key     = OAPI_KEY_N;
	spec.msgproc = NotesMFD::MsgProc;
	oapiRegisterMFDMode(spec);

	mode = MODE_SELECTION;
	opened = false;
	startFileIndex = 0;
	startLineIndex = 0;
	fileIndex = 0;
	strcpy(lastshipname,"");
	//sprintf(lastshipname,"-init-");
}

DLLCLBK void ExitModule(HINSTANCE hModule) {
	if (filesList.files != NULL) {
		free(filesList.files);
	}
}

NotesMFD::NotesMFD (DWORD w, DWORD h, VESSEL *vessel) : MFD (w, h, vessel) {
	Ship=vessel;
	
}

int NotesMFD::MsgProc (UINT msg, UINT mfd, WPARAM wparam, LPARAM lparam) {
	switch (msg) {
		case OAPI_MSG_MFD_OPENED:
			return (int)(new NotesMFD(LOWORD(wparam), HIWORD(wparam), (VESSEL*)lparam));
	}
	return 0;
}

void addfile(flist *list, WIN32_FIND_DATA data) {
	if (list->num_entries == list->max_entries) {
		int newsize = list->max_entries == 0 ? 16 : list->max_entries * 2;
		WIN32_FIND_DATA *temp = (WIN32_FIND_DATA *) realloc(list->files, newsize * sizeof(WIN32_FIND_DATA));
		if (temp != NULL) {
			list->max_entries = newsize;
			list->files = temp;
		} else {
			sprintf (oapiDebugString(), "Out of memory");
		}
	}
	list->files[list->num_entries++] = data;
}

int sortfiles(const void *a, const void *b) {
  const WIN32_FIND_DATA *pa = (WIN32_FIND_DATA *) a;
  const WIN32_FIND_DATA *pb = (WIN32_FIND_DATA *) b;
  return(strcmp(pa->cFileName, pb->cFileName));
}

bool NotesMFD::IsLineBreak(int lineIndex) {
	for (int i = 0; i < lineBreaks.size(); i++) {
		if (lineBreaks[i] == lineIndex) {
			return true;
		}
	}
	return false;
}

int NotesMFD::GetMfdLinesCount() {
	return H / ch - 1;
}

void NotesMFD::ShowNoFiles(HDC hDC) {
	const char *str = "No files found.";
	TextOut(hDC, cw, ch, str, strlen(str));
}

void NotesMFD::ShowList(HDC hDC) {
	if (!listLoaded) {
		char  olddir[MAX_PATH];
		if (GetCurrentDirectory(MAX_PATH, olddir) == 0) {
		  sprintf (oapiDebugString(), "Error on getting files list");
		  return;
		}

		if (!SetCurrentDirectory("(notes)")) {
		  ShowNoFiles(hDC);
		  return;
		}

		HANDLE h;
		WIN32_FIND_DATA info;

		h = FindFirstFile("*.txt", &info);
		if (h != INVALID_HANDLE_VALUE) {
			do {
				if (!(strcmp(info.cFileName, ".") == 0 || strcmp(info.cFileName, "..") == 0)) {
					if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
						addfile(&filesList, info);
					}
				}
			} while (FindNextFile(h, &info));
			if (GetLastError() != ERROR_NO_MORE_FILES) {
				sprintf (oapiDebugString(), "Error on getting files list");
			}
			FindClose(h);
		}

		SetCurrentDirectory(olddir);

		if (filesList.num_entries > 0) {
			qsort(filesList.files, filesList.num_entries, sizeof(filesList.files[0]), sortfiles);
		}

		listLoaded = true;
	}

	if (filesList.num_entries > 0) {
		COLORREF defaultColor = GetTextColor(hDC);;
		int mfdLines = GetMfdLinesCount();
		const char *str;
		for (int i = 0; i < mfdLines; i++) {
			if (startFileIndex + i >= filesList.num_entries) {
				break;
			}
			str = filesList.files[startFileIndex + i].cFileName;
			if (startFileIndex + i == fileIndex) {
				SetTextColor(hDC, RGB(200, 200, 200));
			}
			TextOut(hDC, cw, ch + ch * i, str, strlen(str) - 4);
			if (startFileIndex + i == fileIndex) {
				SetTextColor(hDC, defaultColor);
			}
		}
	} else {
		ShowNoFiles(hDC);
	}
}

void NotesMFD::ReadFile(string path) {
	char dstr[512];
		int linecounter=0;
		lines.clear();
		lineBreaks.clear();
		preidxes.clear();

#ifdef DEBUG		
		sprintf(dstr,"NotesMFD: open '%s'...",path.data());oapiWriteLog(dstr);
#endif
		ifstream in(path.data(), ios::in | ios::binary);
		if(!in) {
			sprintf(dstr,"Cannot open '%s'",path.data());
			string err(dstr);
			lines.push_back(string(err));
			lineBreaks.push_back(lines.size() - 1);
			preidxes.push_back(0);
		} else {
		///// fixme copypasta, make function(filename)
		string line, sub;
		int lineWidth = W / cw - 2;
		char dstr[1024];
		while (getline(in, line)) {
			linecounter++;
			size_t startidx = 0;
			size_t foundidx;
			string from("\t");
			int preidx=0;


			while((foundidx = line.find(from, startidx)) != string::npos) {
				string to("        ");
				if(startidx==0) { 
					preidx=(int)foundidx;
				}
				int rem;
				rem=TABLENGTH - (foundidx % TABLENGTH);
				to.resize(rem);
				line.replace(foundidx, from.size(), to);
				startidx = foundidx + to.size();
			}

			if (lines.size() > 0) {
				lineBreaks.push_back(lines.size() - 1);
			}

			int index = line.find_last_of("\r");
#ifdef SPAMMYDEBUG
			sprintf(dstr,"NotesMFD - %s - Line: idx: %d, npos: %d, str: '%s'",
				path.data(), index, string::npos, line.data()
			);
			oapiWriteLog(dstr);
#endif
			if (index != string::npos) {
				line = line.substr(0, index);
			}
			
			while (line.size() > lineWidth) {
				int spaceIndex = line.find_last_of(" ", lineWidth);
				if (spaceIndex != string::npos) {
					sub = line.substr(0, spaceIndex);
					preidxes.push_back(preidx);
					preidx=0;
					lines.push_back(sub);
					line = line.substr(spaceIndex + 1);
				} else {
					break;
				}
			}

			preidxes.push_back(preidx);
			preidx=0;

			lines.push_back(line);
		}
		in.close();
#ifdef DEBUG
		sprintf(dstr,"NotesMFD: read %d lines from '%s'.",linecounter,path.data());oapiWriteLog(dstr);
#endif
	  }	  
}

void NotesMFD::ShowLines(HDC hDC) {

	SetTextColor(hDC, RGB(0, 200, 0));

	int mfdLines = GetMfdLinesCount();
	const char *str;
	int lineIndex;
	HPEN linePen = CreatePen(PS_DOT, 1, RGB(70, 70, 100));
	HGDIOBJ prevPen = SelectObject(hDC, linePen);
	for (int i = 0; i < mfdLines; i++) {
		lineIndex = startLineIndex + i;
		if (lineIndex >= lines.size()) {
			break;
		}
		str = lines.at(lineIndex).data();
		//TextOut(hDC,cw, ch + ch * i, "(*)", 3);
		
		TextOut(hDC, cw, ch + ch * i, str, strlen(str));
		
		if (preidxes[lineIndex] > 0) {
			SetTextColor(hDC, RGB(150, 250, 150));
			TextOut(hDC, cw, ch + ch * i, str, preidxes[lineIndex]);
			SetTextColor(hDC, RGB(0, 200, 0));
		}
		if (IsLineBreak(lineIndex)) {
			MoveToEx(hDC, 5, 2 * ch + ch * i + 1, NULL);
			LineTo(hDC, W - 5, 2 * ch + ch * i + 1);
		}
	}
	SelectObject(hDC, prevPen);
	DeleteObject(linePen);
}
void NotesMFD::ShowShipHelpAuto(HDC hDC) {
	char shipname[256];
	char dstr[512];
	sprintf(shipname,"%s",Ship->GetClassName());
	// FIXME: Find cheap / safe / case insensitive multi-string cmp
	if( (!strncmp(shipname,"Spacecraft3",256)) || (!strncmp(shipname,"Spacecraft\\Spacecraft3",256)) ) {
		sprintf(shipname,"%s",Ship->GetName());
	}

	if(strncmp(shipname,lastshipname,256)) {
#ifdef DEBUG
		sprintf(dstr,"NotesMFD: Auto - '%s' -> '%s'",lastshipname,shipname); oapiWriteLog(dstr);
#endif
		sprintf(lastshipname,"%s",shipname);
		sprintf(dstr,"NotesMFD: Auto - checking for '%s.txt'...",shipname);
		opened = false;
	}

	Title(hDC, shipname);

	if(!opened) {
		opened = true;
		string path = "Doc\\keyhelp\\";
		path.append(shipname);
		path.append(".txt");
		ReadFile(path.data());
	}
	ShowLines(hDC);
}


void NotesMFD::ShowContent(HDC hDC) { // fixme - code duplication
	if (!opened) {
		if (fileIndex > filesList.num_entries - 1) {
			ShowNoFiles(hDC);
			return;
		}
		opened = true;
		string path = "(notes)\\";
		path.append(filesList.files[fileIndex].cFileName);
		ReadFile(path.data());
		fileName = filesList.files[fileIndex].cFileName;
		fileName = fileName.substr(0, fileName.size() - 4);
	}
	
	Title(hDC, fileName.data());

	ShowLines(hDC);
}

void NotesMFD::Update (HDC hDC) {
	if (mode == MODE_SELECTION) {
		Title(hDC, "Select file");
		SetTextColor(hDC, RGB(0, 200, 0));
		ShowList(hDC);
	} else if(mode == MODE_AUTO) {
		ShowShipHelpAuto(hDC);
	} else {
		ShowContent(hDC);
	}
}

bool NotesMFD::ConsumeKeyBuffered (DWORD key)
{
	int mfdLines = GetMfdLinesCount();
	bool keyProcessed = false;
	switch (key) {
		case OAPI_KEY_L:
			mode = MODE_SELECTION;
			opened = false;
			keyProcessed = true;
			break;
		case OAPI_KEY_S:
			if (mode == MODE_SELECTION && listLoaded) {
				mode = MODE_VIEW;
				startLineIndex = 0;
			}
			keyProcessed = true;
			break;
		case OAPI_KEY_UP:
			if (mode == MODE_SELECTION) {
				if (listLoaded) {
					fileIndex--;
					if (fileIndex < 0) {
						fileIndex = 0;
					}
					if (fileIndex < startFileIndex) {
						startFileIndex = fileIndex;
					}
				}
			} else {
				if (opened) {
					startLineIndex--;
					if (startLineIndex < 0) startLineIndex = 0;
				}
			}
			keyProcessed = true;
			break;
		case OAPI_KEY_DOWN:
			if (mode == MODE_SELECTION) {
				if (listLoaded) {
					fileIndex++;
					if (fileIndex > filesList.num_entries - 1) {
						fileIndex = filesList.num_entries - 1;
					}
					if (fileIndex - startFileIndex > mfdLines - 1) {
						startFileIndex = fileIndex - mfdLines + 1;
					}
				}
			} else {
				if (opened) {
					if (lines.size() > mfdLines) {
						startLineIndex++;
						if (startLineIndex > lines.size() - mfdLines) {
							startLineIndex = lines.size() - mfdLines;
						}
					}
				}
			}
			keyProcessed = true;
			break;
		case OAPI_KEY_PRIOR:
			if (mode == MODE_SELECTION) {
				if (listLoaded) {
					fileIndex -= mfdLines;
					if (fileIndex < 0) {
						fileIndex = 0;
					}
					if (fileIndex < startFileIndex) {
						startFileIndex = fileIndex;
					}
				}
			} else {
				if (opened) {
					startLineIndex -= mfdLines;
					if (startLineIndex < 0) startLineIndex = 0;
				}
			}
			keyProcessed = true;
			break;
		case OAPI_KEY_NEXT:
			if (mode == MODE_SELECTION) {
				if (listLoaded) {
					fileIndex += mfdLines;
					if (fileIndex > filesList.num_entries - 1) {
						fileIndex = filesList.num_entries - 1;
					}
					if (fileIndex - startFileIndex > mfdLines - 1) {
						startFileIndex = fileIndex - mfdLines + 1;
					}
				}
			} else {
				if (opened) {
					if (lines.size() > mfdLines) {
						startLineIndex += mfdLines;
						if (startLineIndex > lines.size() - mfdLines) {
							startLineIndex = lines.size() - mfdLines;
						}
					}
				}
			}
			keyProcessed = true;
			break;
		case OAPI_KEY_SLASH:
			mode = MODE_AUTO;
			opened = false;
			keyProcessed = true;
			break;
	}
	if (keyProcessed) {
		InvalidateDisplay();
	}
	return false;
}

bool NotesMFD::ConsumeButton (int bt, int event)
{
	static const DWORD btkey[OPTS] = { OAPI_KEY_L, OAPI_KEY_S, OAPI_KEY_UP, OAPI_KEY_DOWN, OAPI_KEY_PRIOR, OAPI_KEY_NEXT, OAPI_KEY_SLASH};

	if (event & PANEL_MOUSE_LBDOWN) { 
//		static const DWORD btkey[OPTS] = { OAPI_KEY_L, OAPI_KEY_S, OAPI_KEY_U, OAPI_KEY_D, OAPI_KEY_B, OAPI_KEY_F, OAPI_KEY_SLASH};
		if (bt < OPTS) {
			btPressedTime = 0;
			btPressedFirstStep = true;
			return ConsumeKeyBuffered (btkey[bt]);
		} else {
			return false;
		}
	} else if (event & PANEL_MOUSE_LBPRESSED) { 
//		static const DWORD btkey[OPTS] = { OAPI_KEY_L, OAPI_KEY_S, OAPI_KEY_U, OAPI_KEY_D, OAPI_KEY_B, OAPI_KEY_F, OAPI_KEY_SLASH};
		btPressedTime += oapiGetSysStep();
		double step = btPressedFirstStep ? 0.3 : 0.1;
		if ((bt >= 2 && bt <= 5) && btPressedTime > step) {
			btPressedTime = 0;
			btPressedFirstStep = false;
			return ConsumeKeyBuffered (btkey[bt]);
		} else {
			return false;
		}
	} else {
		return false;
	}
}

char *NotesMFD::ButtonLabel (int bt) {
	char *label[OPTS] = {"LST", "SEL", "UP", "DN", "PUP", "PDN", "KEY"};
	return (bt < OPTS ? label[bt] : 0);
}

int NotesMFD::ButtonMenu (const MFDBUTTONMENU **menu) const
{
	static const MFDBUTTONMENU mnu[OPTS] = {
		{"List files", 0, 'L'},
		{"Select", 0, 'S'},
		{"Scroll up","(Up Arrow)", '\0'},
		{"Scroll down","(Down Arrow)", '\0'},
		{"Page up","(PgUp)", '\0'},
		{"Page down","(PgDn)", '\0'},
		{"Keys", 0, '?'},
	};
	if (menu) *menu = mnu;
	return OPTS;
}
